/*
    Copyright 2006-2011 Patrik Jonsson, sunrise@familjenjonsson.org

    This file is part of Sunrise.

    Sunrise is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    Sunrise is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Sunrise.  If not, see <http://www.gnu.org/licenses/>.

*/

/// \file
/// Implementations of the non-templated camera_base methods.

// $Id$

#include "emergence.h"
#include "emergence-fits.h"
#include "vecops.h"
#include "mcrxversion.h"

using namespace std;

/** Constructor. This assumes the camera is pointed at the origin and
    that we are interested in specifying the (theta, phi) position,
    distance, and a \b linear field-of-view. theta and phi are
    specified in radians. */
mcrx::camera_base::camera_base(T_float cd, T_float T,
			       T_float p, int ysize, 
			       const vec3d& translate_origin,
			       boost::shared_ptr<projection>& proj) :
  proj_(proj),
  axis_ (int(floor(ysize*proj_->aspect_ratio())), ysize),
  cameradist (cd),
  theta (T), phi (p), intensity (0), 
  // the direction the camera is pointing at
  dir_ (-sin(theta)*cos(phi), -sin(theta)*sin(phi), -cos(theta)),
  pos_ (-dir_*cd + translate_origin),
  // vector orthogonal to dir
  ny_ (cos (theta)*cos (phi), cos (theta)*sin (phi), -sin (theta)),
  nx_ (cross (ny_, dir_)),
  use_mutex (true)
{
}


/** Constructor. This is a general-purpose constructor which takes a
    predefined projection object and an image size.  */
mcrx::camera_base::camera_base (const vec3d& pos, const vec3d& dir, 
				const vec3d& up,
				int ysize,
				boost::shared_ptr<projection>& proj) :
  proj_(proj),
  axis_ (int(floor(ysize*proj_->aspect_ratio())), ysize),
  cameradist(sqrt(dot(pos,pos))),
  theta (atan(pos[2]/ sqrt(pos[0]*pos[0] + pos[1]*pos[1]))),
  phi (atan2(pos[1], pos[0])),
  intensity (0), 
  dir_ (dir/sqrt(dot(dir,dir))),
  pos_ (pos),
  // ny is the normalized projection of up orthogonal to lpn, standard
  // Gram-Schmidt stuff. (It is the responsibility of the user to make
  // sure this is nonsingular, but it does not have to be normalized)
  ny_ ((up - dot(dir_, up)*dir_)/sqrt(dot((up - dot(dir_, up)*dir_),
					  (up - dot(dir_, up)*dir_)))),
  nx_ (cross (ny_, dir_)),
  use_mutex (true)
{}


void mcrx::camera_base::write_parameters (CCfits::ExtHDU& hdu,
					  const T_unit_map& u) const
{
  write_history (hdu);
  
  proj_->write_parameters(hdu, u);

  // write all the quantities as keywords to the specified HDU
  hdu.addKey("cameradist", cameradist,
	     "[" + u.get("length") +
	     "] Distance from origin to camera");

  hdu.addKey("theta", theta,
	     "[rad] Angular coordinate theta of camera position");
  hdu.addKey("phi", phi,
	     "[rad] Angular coordinate phi of camera position");
  hdu.addKey("intensity", intensity,
	     "[?] Total intensity in image");
  hdu.addKey("xsize", xsize(),
	     "Number of pixels in x-direction");
  hdu.addKey("ysize", ysize(), 
	     "Number of pixels in y-direction");

  hdu.addKey("linear_fov", sqrt(dot(pos_,pos_)) * proj_->scale(),
	     "["+u.get("length") + "] Linear field of view in y-dir at origin");
  hdu.addKey("camposx", pos_[0],
	     "["+u.get("length") + "] X position of camera");
  hdu.addKey("camposy", pos_[1],
	     "["+u.get("length") + "] Y position of camera");
  hdu.addKey("camposz", pos_[2],
	     "["+u.get("length") + "] Z position of camera");

  // note that we write the negative of lpn, because that is what the
  // constructor takes and is the direction in which the camera is
  // viewing.
  hdu.addKey("camdirx", dir_[0],
	     "["+u.get("length") + "] X comp of camera viewing dir");
  hdu.addKey("camdiry", dir_[1],
	     "["+u.get("length") + "] Y comp of camera viewing dir");
  hdu.addKey("camdirz", dir_[2],
	     "["+u.get("length") + "] Z comp of camera viewing dir");
  hdu.addKey("camupx", ny_[0],
	     "["+u.get("length") + "] X comp of camera Y axis dir");
  hdu.addKey("camupy", ny_[1],
	     "["+u.get("length") + "] Y comp of camera Y axis dir");
  hdu.addKey("camupz", ny_[2],
	     "["+u.get("length") + "] Z comp of camera Y axis dir");

  // The FITS coordinate keywords are written to the image HDU itself
}

/// Writes a string indicating processing history to an HDU.
void mcrx::camera_base::write_history (CCfits::HDU & output) const
{
  ostringstream history;
  history << "This HDU was created by Sunrise (class camera_base) version "
	  << mcrx::version() << ", on ";
  time_t now = time (0) ;
  char*now_string = ctime (& now);
  now_string [24] = 0;
  history << now_string << " by user ";
  const char* u=getenv ("USER");
  assert(u != 0);
  string user (u);
  const char* h=getenv ("HOST");
  if(h == 0)
    h = getenv ("HOSTNAME");
  if(h == 0) {
    cerr << "Can't determine host name, HOST or HOSTNAME not set.\n";
    h="UNKNOWN";
  }
  string host (h);
  history << user << " on host "<< host << ".";
  output.writeHistory(history.str());
}
 


/** Writes WCS keywords to the specified HDU.  The WCS is in linear
 units so that we actually get the physical dimensions of the
 simulation volume depicted, instead of angular units which would be
 pointless and hard to use. */
void mcrx::camera_base::write_wcs (CCfits::ExtHDU& hdu,
				   const T_unit_map& units) const
{
  hdu.addKey("CTYPE1", std::string ("linear"),"");
  hdu.addKey("CTYPE2", std::string ("linear"),"");
  hdu.addKey("CRPIX1", axis_[0]*0.5,"");
  hdu.addKey("CRPIX2", axis_[1]*0.5,"");
  hdu.addKey("CRVAL1",0,"");
  hdu.addKey("CRVAL2",0,"");
  hdu.addKey("CD1_1",proj_->scale()*cameradist/axis_[0],
	     "[" + units.get("length") + "]" );
  hdu.addKey("CD1_2",0,
	     "[" + units.get("length") + "]" );
  hdu.addKey("CD2_1",0,
	     "[" + units.get("length") + "]" );
  hdu.addKey("CD2_2",proj_->scale()*cameradist/axis_[1],
	     "[" + units.get("length") + "]" );
}
